 <template>
   <div class="deploymentManagement_container">
     <div class="tech-back-btn" @click="handleBack">
       <div class="btn-content">
         <el-icon :size="18"><ArrowLeftBold /></el-icon>
         <span>返回</span>
       </div>
       <div class="border-beam"></div>
     </div>
     <MCard class="m-t-20px">
      <template #header>
        <div class="deployPlatformDetail_header">
          <span>部署管理</span>
        </div>
      </template>
      <div>
        <div class="tech-info-display">
          <!-- 数据展示区域 -->
          <div class="tech-info-section">
            <div class="tech-info-item" v-for="(_,key) in deployInfoItems" :key="key" >
              <div class="info-label">
                <div class="label-icon"></div>
                <span>{{ deployInfoItems[key] }}:</span>
              </div>
              <div class="info-value">{{ deployInfo[key]}}</div>
            </div>
          </div>
          <div class="tech-info-message">
            <div class="message-label">
              <span>启动脚本:</span>
              <div class="scanning-line"></div>
            </div>
            <div class="message-content flex justify-between gap-10px items-center">
              <p>{{ deployInfo.startScript || '暂无启动脚本' }}</p>
              <div class="global_btn"  @click="handleAddStartupScript">
                  <!-- 添加图标 -->
                  <el-icon :size="18"><Plus /></el-icon>
                  <span>添加启动脚本</span>
                </div>
            </div>
          </div>
          <!-- 装饰性元素 -->
          <div class="tech-decoration top-left"></div>
          <div class="tech-decoration top-right"></div>
          <div class="tech-decoration bottom-left"></div>
          <div class="tech-decoration bottom-right"></div>
        </div>
      </div>
    </MCard>
    <MCard>
      <template #header>
        <div class="flex gap-20px items-center">
          <span>已部署节点</span>
          <div class="select-script-type">
            <el-radio-group v-model="nodeChooseConfig.nodeTypeRadio" @change="(val: string | number | boolean | undefined) => handleNodeTypeChange(val as string)" class="tech-radio-group">
              <el-radio value="2" size="large">全部节点</el-radio>
              <el-radio value="1" size="large">真实节点</el-radio>
              <el-radio value="0" size="large">虚拟节点</el-radio>
            </el-radio-group>
          </div>
        </div>
      </template>
      <div class="flex gap-30px items-center">
        <div class="global_btn" v-globalBtnLoading="{ handler: handleRunDeployedNode, text: '运行中...' }">一键运行</div>
         <div class="global_btn" v-globalBtnLoading="{ handler: handleStopDeployedNode, text: '停止中...' }">一键停止</div>
         <div class="global_btn" v-globalBtnLoading="{ handler: handleDeleteDeployedNode, text: '删除中...' }">一键删除</div>  
         <div class="tech-checkbox" @click="toggleSelectAll">
           <div class="checkbox-inner" :class="{'is-checked': nodeChooseConfig.isSelectAll}">
             <div class="check-mark" v-if="nodeChooseConfig.isSelectAll"></div>
           </div>
           <span class="checkbox-label">全选</span>
         </div>
      </div>
      <div class="grid grid-cols-4 gap-10px m-t-20px">
        <div class="deployedNodeItem" v-for="(item,index) in deployedNodeList" :key="index">
            <div class="node-header">
              <div class="tech-checkbox" @click="toggleItemSelect(item)">
                <div class="checkbox-inner" :class="{'is-checked': item.isSelect}">
                  <div class="check-mark" v-if="item.isSelect"></div>
                </div>
              </div>
              <span class="node-id">节点ID: {{ item.nodeId }}</span>
            </div>
            <div class="node-divider"></div>
            <div class="node-content">
              <div class="node-info">
                <div class="info-grid">
                  <div class="info-item">
                    <span class="info-label">节点类型:</span>
                    <span class="info-value" :class="item.type===1 ? 'real-node' : 'virtual-node'">{{ item.type===1 ? '真实节点' : '虚拟节点' }}</span>
                  </div>
                  <div class="info-item" v-for="(_,key) in deployedNodeListItems" :key="key">
                    <span class="info-label">{{ deployedNodeListItems[key] }}:</span>
                    <span class="info-value">{{ item[key] }}</span>
                  </div>
                  <div class="info-item">
                    <span class="info-label">运行状态:</span>
                    <span class="info-value" :class="item.runningStatus === 1 ? 'real-node' : 'virtual-node'">{{ item.runningStatus === 1 ? '运行中' : '未运行' }}</span>
                  </div>
                </div>
                <div class="status-indicator" :class="item.connectionStatus === 1 ? 'connected' : 'disconnected'">
                  <img :src="item.imgUrl" alt="" class="status-icon">
                  <div class="status-pulse"></div>
                  <div class="status-text">{{ item.connectionStatus === 1 ? '已连接' : '未连接' }}</div>
                </div>
              </div>
              <div class="node-actions">
                <div class="global_btn" style="height: 25px;"  >节点详情</div>
                <div class="global_btn" style="height: 25px;" @click="handleDeploymentDetail(item)">部署详情</div>
              </div>
            </div>
        </div>
      </div>
       <div class="flex justify-center m-t-20px">
        <el-pagination
            v-model:currentPage="deployedNodePageConfig.currentPage"
            v-model:page-size="deployedNodePageConfig.pageSize"
            :page-sizes="[3,5,7,10]"
            layout="total, prev, pager, next, jumper"
            :total="deployedNodePageConfig.total"
            @size-change="getDeployedNodeList"
            @current-change="getDeployedNodeList"
            size="small"
           />
       </div>
    </MCard>
    <MCard>
      <template #header>
        <div class="flex gap-20px items-center">
          <span>可部署节点</span>
          <div class="select-script-type">
            <el-radio-group v-model="unDeployedNodeChooseConfig.nodeTypeRadio" @change="(val: string | number | boolean | undefined) => handleUnNodeTypeChange(val as string)" class="tech-radio-group">
              <el-radio value="2" size="large">全部节点</el-radio>
              <el-radio value="1" size="large">真实节点</el-radio>
              <el-radio value="0" size="large">虚拟节点</el-radio>
            </el-radio-group>
          </div>
        </div>
      </template>
      <div class="flex gap-30px items-center">
         <div class="global_btn" v-globalBtnLoading="{ handler: handleDeployUnDeployedNode, text: '部署中...' }">一键部署</div>
         <!-- <div class="global_btn" v-globalBtnLoading="{ handler: handleDeleteUnDeployedNode, text: '删除中...' }">一键删除</div> -->
         <div class="tech-checkbox" @click="toggleUnDeployedSelectAll">
           <div class="checkbox-inner" :class="{'is-checked': unDeployedNodeChooseConfig.isSelectAll}">
             <div class="check-mark" v-if="unDeployedNodeChooseConfig.isSelectAll"></div>
           </div>
           <span class="checkbox-label">全选</span>
         </div>
      </div>
      <div class="grid grid-cols-4 gap-10px m-t-20px">
        <div class="deployedNodeItem" v-for="(item,index) in unDeployedNodeList" :key="index">
            <div class="node-header">
              <div class="tech-checkbox" @click="toggleUnDeployedItemSelect(item)">
                <div class="checkbox-inner" :class="{'is-checked': item.isSelect}">
                  <div class="check-mark" v-if="item.isSelect"></div>
                </div>
              </div>
              <span class="node-id">节点ID: {{ item.nodeId }}</span>
            </div>
            <div class="node-divider"></div>
            <div class="node-content">
              <div class="node-info">
                <div class="info-grid">
                  <div class="info-item">
                    <span class="info-label">节点类型:</span>
                    <span class="info-value" :class="item.type===1 ? 'real-node' : 'virtual-node'">{{ item.type===1 ? '真实节点' : '虚拟节点' }}</span>
                  </div>
                  <div class="info-item" v-for="(_,key) in deployedNodeListItems" :key="key">
                    <span class="info-label">{{ deployedNodeListItems[key] }}:</span>
                    <span class="info-value">{{ item[key] }}</span>
                  </div>
                  
                </div>        
              </div>
              <div class="node-actions">
                <div class="global_btn" style="height: 25px;">节点详情</div>
                <!-- <div class="global_btn" style="height: 25px;">部署详情</div> -->
              </div>
            </div>
        </div>
      </div>
       <div class="flex justify-center m-t-20px">
        <el-pagination
            v-model:currentPage="unDeployedNodePageConfig.currentPage"
            v-model:page-size="unDeployedNodePageConfig.pageSize"
            :page-sizes="[3,5,7,10]"
            layout="total, prev, pager, next, jumper"
            :total="unDeployedNodePageConfig.total"
            @size-change="getUnDeployedNodeList"
            @current-change="getUnDeployedNodeList"
            size="small"
           />
       </div>
    </MCard>
    <MDialog 
    v-model="startupScriptDialogConfig.visible" 
     v-bind="startupScriptDialogConfig"
    >
    <template #content>
      <!-- 启动脚本文本域 -->
       <el-form labelPosition="left" labelWidth="100px">
        <!-- 提供选择框让用户选择文件还是输入 -->
        <el-form-item label="上传方式">
          <div class="select-script-type">
            <el-radio-group v-model="startupScriptDialogConfig.selectType" class="tech-radio-group">
              <el-radio value="1">脚本文件</el-radio>
              <el-radio value="2">手动输入</el-radio>
            </el-radio-group>
          </div>
        </el-form-item>
        <el-form-item label="启动脚本"  v-show="startupScriptDialogConfig.selectType === '2'">
          <el-input type="textarea"  v-model="startupScriptDialogConfig.startScript" autosize/>
        </el-form-item>
        <el-form-item label="启动脚本文件" v-show="startupScriptDialogConfig.selectType === '1'">
          <UploadFile v-model:file="startupScriptDialogConfig.file" />
        </el-form-item>
       </el-form>
       <div class="flex justify-end gap-20px">
        <div class="global_btn"   style="width: 100px;"
        v-globalBtnLoading="{ handler: handleSubmitStartupScript, text: '提交中...' }">提交</div>
      </div>
    </template>      
    </MDialog>
   </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue'
 export default defineComponent({
   name: 'DeploymentManagement',
 })
 </script>
 <script setup  lang="ts">
import { useRoute,useRouter } from "vue-router"
import { deployPlatformApi } from "@/api/deployPlatform"
import { ArrowLeftBold } from '@element-plus/icons-vue'
import { nodeManagementApi } from "@/api/nodeManagement"
import connectedImg from "@/assets/imgs/connected.png"
import disconnectedImg from "@/assets/imgs/disconnected.png"
import {ElNotificationFn} from "@/utils/computedUtils"
import { Plus } from '@element-plus/icons-vue'
import dayjs from "dayjs"
const route = useRoute()
const router = useRouter()
const deploymentId = route.query.deploymentId
const deployInfo = ref<any>({
  deploymentName:"",
  deploymentId:"",
  versionNumber:"",
  commitId:"",
  createdTime:"",
  commitMessage:""
})
const deployInfoItems  = ref<any>({
  deploymentName:"文件名称",
  deploymentId:"文件ID",
  versionNumber:"版本号",
  commitId:"提交ID",
  createdTime:"创建时间",
  commitMessage:"提交信息",
})
async function getDeployInfo(){
  try {
    const {data} = await deployPlatformApi.getDeployPlatformListByCondition({
      deploymentId
    })
    deployInfo.value = data[0]
    deployInfo.value.createdTime = dayjs(deployInfo.value.createdTime).format("YYYY-MM-DD HH:mm:ss")
  } catch (error) {}
}
function handleBack(){
  router.back()
}
const nodeChooseConfig = ref<any>({
  nodeTypeRadio:"2",
  isSelectAll:false
})
function toggleSelectAll() {
  nodeChooseConfig.value.isSelectAll = !nodeChooseConfig.value.isSelectAll
  deployedNodeList.value.forEach((item: any) => {
    item.isSelect = nodeChooseConfig.value.isSelectAll
  })
}
function toggleItemSelect(item: any) {
  item.isSelect = !item.isSelect
  nodeChooseConfig.value.isSelectAll = deployedNodeList.value.every((node: any) => node.isSelect)
}
const deployedNodePageConfig = ref<any>({
  pageSize:4,
  currentPage:1,
  total:0
})
const deployedNodeList = ref<any>([])
const deployedNodeListItems = ref<any>({
  ipAddress:"IP地址",
  deploymentFileNumber:"部署文件",
  updateTime:"更新时间"
})
const initialDeployedNodeList = ref<any>([])
async function getDeployedNodeList(){
  try {
    nodeChooseConfig.value.nodeTypeRadio ="2"
    const {data} = await nodeManagementApi.postDeployedNodeList({
      pageSize:deployedNodePageConfig.value.pageSize,
    currentPage:deployedNodePageConfig.value.currentPage,
    deploymentId,
  })
  initialDeployedNodeList.value = data.records.map((item:any)=>{
    return {
      ...item,
      isSelect:false,
      updateTime:dayjs(item.updateTime).format("YYYY-MM-DD HH:mm:ss"),
      imgUrl:item.connectionStatus=== 1 ? connectedImg : disconnectedImg
    }
  })
  deployedNodeList.value = initialDeployedNodeList.value
  deployedNodePageConfig.value.total = data.total
  } catch (error) { }
}
function handleNodeTypeChange(val:string){
  if(val === "2"){
    deployedNodeList.value = initialDeployedNodeList.value
    return
  }
  deployedNodeList.value = initialDeployedNodeList.value.filter((item:any)=>{
    return item.type == val
  })
}
async function handleStopDeployedNode(){
  try { 
    if(!deployedNodeList.value.some((item:any)=>item.isSelect)){
      ElNotificationFn("warning","至少选择一个节点")
      return
  }
  await deployPlatformApi.issueStopDeployedNode({
    deploymentId,
    ips:deployedNodeList.value.filter((item:any)=>item.isSelect).map((item:any)=>item.ipAddress)
  })
  ElNotificationFn("success","停止成功")
  getDeployedNodeList()
  } catch (error) {}
}
async function handleDeleteDeployedNode(){
  try {
    if(!deployedNodeList.value.some((item:any)=>item.isSelect)){
      ElNotificationFn("warning","至少选择一个节点")
      return
  }
  await deployPlatformApi.issueDeleteDeployedNode({
    deploymentId,
    ips:deployedNodeList.value.filter((item:any)=>item.isSelect).map((item:any)=>item.ipAddress)
  })
  await new Promise(resolve=>setTimeout(resolve,3000))
  await getDeployedNodeList()
  ElNotificationFn("success","删除成功")
  } catch (error) {}
}
const unDeployedNodeList = ref<any>([])
const initialUnDeployedNodeList = ref<any>([])
const unDeployedNodePageConfig = ref<any>({
  pageSize:4,
  currentPage:1,
  total:0
})
const unDeployedNodeChooseConfig = ref<any>({
  nodeTypeRadio:"2",
  isSelectAll:false
})
async function getUnDeployedNodeList(){
  try {
    unDeployedNodeChooseConfig.value.nodeTypeRadio = "2"
    const {data} = await nodeManagementApi.postUndeployedNodeList({
    pageSize:unDeployedNodePageConfig.value.pageSize,
    currentPage:unDeployedNodePageConfig.value.currentPage,
    deploymentId,
  })
  initialUnDeployedNodeList.value = data.records.map((item:any)=>{
    return {
      ...item,
      isSelect:false,
      updateTime:dayjs(item.updateTime).format("YYYY-MM-DD HH:mm:ss"),
    }
  })
  unDeployedNodeList.value = initialUnDeployedNodeList.value
  unDeployedNodePageConfig.value.total = data.total
  } catch (error) { }
}
function handleUnNodeTypeChange(val:string){
  if(val === "2"){
    unDeployedNodeList.value = initialUnDeployedNodeList.value
    return
  }
  unDeployedNodeList.value = initialUnDeployedNodeList.value.filter((item:any)=>{ 
    return item.type == val
  })
}
function toggleUnDeployedItemSelect(item:any){
  item.isSelect = !item.isSelect
  unDeployedNodeChooseConfig.value.isSelectAll = unDeployedNodeList.value.every((node:any)=>node.isSelect)
}
function toggleUnDeployedSelectAll(){
  unDeployedNodeChooseConfig.value.isSelectAll = !unDeployedNodeChooseConfig.value.isSelectAll
  unDeployedNodeList.value.forEach((item:any)=>{
    item.isSelect = unDeployedNodeChooseConfig.value.isSelectAll
  })
}
async function handleDeployUnDeployedNode(){
try {
  if(!unDeployedNodeList.value.some((item:any)=>item.isSelect)){
    ElNotificationFn("warning","至少选择一个节点")
    return
 }
 if(!deployInfo.value.startScript){
  ElNotificationFn("warning","暂无启动脚本,配置启动脚本后可一键部署")
  return
 }
 await deployPlatformApi.issueDeployDeployedNode({
  deploymentId,
  ips:unDeployedNodeList.value.filter((item:any)=>item.isSelect).map((item:any)=>item.ipAddress)
 })
 ElNotificationFn("success","部署成功")
 getDeployedNodeList()
 getUnDeployedNodeList()
} catch (error) {}
}
function handleDeploymentDetail(item:any){
  router.push({
    path:"/deployPlatform/deploymentDetail",
    query:{
      deploymentId,
    }
  })
}
async function handleRunDeployedNode(){
  try {
    if(!deployedNodeList.value.some((item:any)=>item.isSelect)){
      ElNotificationFn("warning","至少选择一个节点")
      return
    }
    await deployPlatformApi.issueRunDeployedNode({
      deploymentId,
      ips:deployedNodeList.value.filter((item:any)=>item.isSelect).map((item:any)=>item.ipAddress)
    })
    ElNotificationFn("success","运行成功")
    getDeployedNodeList()
  } catch (error) {}
}
function handleAddStartupScript(){
  startupScriptDialogConfig.value.visible = true
}
const startupScriptDialogConfig = ref<any>({
  title:"启动脚本配置",
  width:"600px",
  visible:false,
  startScript:"",
  file:null,
  selectType:"1"
})
async function handleSubmitStartupScript(){
  try {
    if(!startupScriptDialogConfig.value.startScript && !startupScriptDialogConfig.value.file){
      ElNotificationFn("warning","启动脚本不能为空")
      return
    }
    const formData = new FormData()
    formData.append("deploymentId",deploymentId as string)
    if(startupScriptDialogConfig.value.selectType === "2"){
      formData.append("startScript",startupScriptDialogConfig.value.startScript)
    }else{
      formData.append("file",startupScriptDialogConfig.value.file)
    }
    await deployPlatformApi.addStartupScript(formData)
    ElNotificationFn("success","提交成功")
    startupScriptDialogConfig.value.visible = false
    startupScriptDialogConfig.value.startScript = ""
    startupScriptDialogConfig.value.file = null
    getDeployInfo()
  } catch (error) {}
}
const fetchTasks = [ 
  {fn:getDeployInfo,key:"获取部署物信息"},
  {fn:getDeployedNodeList,key:"获取已部署节点列表"},
  {fn:getUnDeployedNodeList,key:"获取可部署节点列表"},
]
async function init(){
  const results = await Promise.allSettled(
    fetchTasks.map(task => task.fn())
  )  
  results.forEach((result, index) => {
    if (result.status === 'rejected') {
      console.error(`获取${fetchTasks[index].key}失败:`, result.reason)
    }
  })
}
onMounted(async ()=>{
  await init()
})
 </script>
 
 <style lang="scss" scoped>
 @import "@/styles/card.scss";
 @import "@/styles/pagination.scss";
 @import "@/styles/input.scss";
 @import "@/styles/form.scss";
 @import "@/styles/radio.scss";
 .deploymentManagement_container{
  padding: 20px;
  .tech-back-btn {
  position: relative;
  width: 110px;
  height: 30px;
  background: linear-gradient(
    90deg,
    rgba(18, 30, 45, 0.9),
    rgba(25, 40, 65, 0.95),
    rgba(18, 30, 45, 0.9)
  );
  border: 1px solid rgba(35, 85, 140, 0.5);
  border-radius: 4px;
  cursor: pointer;
  overflow: hidden;
  box-shadow: 
    inset 0 0 10px rgba(0, 140, 255, 0.1),
    0 0 15px rgba(0, 0, 0, 0.4),
    0 0 10px rgba(0, 150, 255, 0.1);
  transition: all 0.3s;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, 
      transparent, 
      rgba(0, 150, 255, 0.5), 
      transparent
    );
    opacity: 0.7;
    z-index: 3;
  }
  
  &::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 10%;
    width: 80%;
    height: 1px;
    background: linear-gradient(90deg, 
      transparent, 
      rgba(0, 150, 255, 0.3), 
      transparent
    );
    opacity: 0.5;
  }
  
  .btn-content {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 100%;
    color: #fff;
    font-size: 14px;
    letter-spacing: 1px;
    text-shadow: 0 0 5px rgba(0, 180, 255, 0.5);
    
    .el-icon {
      color: rgba(0, 180, 255, 0.9);
      filter: drop-shadow(0 0 3px rgba(0, 180, 255, 0.5));
      transition: transform 0.3s;
    }
  }
  
  .border-beam {
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(
      90deg,
      transparent,
      rgba(0, 180, 255, 0.1),
      transparent
    );
    z-index: 1;
    animation: border-scan 3s infinite;
  }
  
  .corner {
    position: absolute;
    width: 7px;
    height: 7px;
    z-index: 5;
    
    &.top-left {
      top: 0;
      left: 0;
      border-top: 1px solid rgba(0, 180, 255, 0.8);
      border-left: 1px solid rgba(0, 180, 255, 0.8);
    }
    
    &.top-right {
      top: 0;
      right: 0;
      border-top: 1px solid rgba(0, 180, 255, 0.8);
      border-right: 1px solid rgba(0, 180, 255, 0.8);
    }
    
    &.bottom-left {
      bottom: 0;
      left: 0;
      border-bottom: 1px solid rgba(0, 180, 255, 0.8);
      border-left: 1px solid rgba(0, 180, 255, 0.8);
    }
    
    &.bottom-right {
      bottom: 0;
      right: 0;
      border-bottom: 1px solid rgba(0, 180, 255, 0.8);
      border-right: 1px solid rgba(0, 180, 255, 0.8);
    }
  }
  
  &:hover {
    background: linear-gradient(
      90deg,
      rgba(22, 35, 55, 0.9),
      rgba(30, 45, 75, 0.95),
      rgba(22, 35, 55, 0.9)
    );
    box-shadow: 
      inset 0 0 15px rgba(0, 140, 255, 0.15),
      0 0 15px rgba(0, 0, 0, 0.4),
      0 0 15px rgba(0, 150, 255, 0.2);
    transform: translateY(-2px);
    
    .btn-content {
      .el-icon {
        transform: translateX(-3px);
      }
    }
    
    &::before, &::after {
      opacity: 1;
    }
  }
  
  &:active {
    transform: translateY(1px);
    box-shadow: 
      inset 0 0 5px rgba(0, 140, 255, 0.05),
      0 0 5px rgba(0, 0, 0, 0.4),
      0 0 5px rgba(0, 150, 255, 0.1);
  }
}

// 科技感全选复选框
.tech-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  padding: 4px 10px;
  border-radius: 4px;
  transition: all 0.3s ease;
  position: relative;
  
  &:hover {
    background: rgba(0, 150, 255, 0.1);
    
    .checkbox-inner {
      border-color: rgba(0, 170, 255, 0.7);
      box-shadow: 0 0 8px rgba(0, 170, 255, 0.3);
    }
  }
  
  &:active {
    background: rgba(0, 150, 255, 0.2);
  }
  
  .checkbox-inner {
    width: 22px;
    height: 22px;
    border: 2px solid rgba(35, 85, 140, 0.6);
    border-radius: 50%;
    background: rgba(18, 30, 45, 0.6);
    position: relative;
    transition: all 0.25s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    
    &::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 1px;
      background: linear-gradient(90deg, 
        transparent, 
        rgba(0, 170, 255, 0.5), 
        transparent
      );
      opacity: 0.7;
    }
    
    &::after {
      content: '';
      position: absolute;
      bottom: 0;
      left: 15%;
      width: 70%;
      height: 1px;
      background: linear-gradient(90deg, 
        transparent, 
        rgba(0, 170, 255, 0.3), 
        transparent
      );
      opacity: 0.5;
    }
    
    &.is-checked {
      background: linear-gradient(
        135deg,
        rgba(0, 85, 150, 0.9),
        rgba(0, 130, 210, 0.9)
      );
      border-color: rgba(0, 170, 255, 0.8);
      box-shadow: 
        inset 0 0 8px rgba(0, 210, 255, 0.3),
        0 0 12px rgba(0, 170, 255, 0.4);
    }
    
    .check-mark {
      position: relative;
      width: 12px;
      height: 8px;
      border-bottom: 2px solid #fff;
      border-left: 2px solid #fff;
      transform: rotate(-45deg);
      filter: drop-shadow(0 0 2px rgba(0, 210, 255, 0.8));
      opacity: 0;
      margin-bottom: 2px;
      margin-right: 1px;
      animation: check-appear 0.3s forwards;
      
      &::before {
        content: '';
        position: absolute;
        top: -2px;
        right: -1px;
        width: 4px;
        height: 4px;
        background: rgba(0, 210, 255, 0.6);
        border-radius: 50%;
        filter: blur(2px);
      }
    }
  }
  
  .checkbox-label {
    color: rgba(255, 255, 255, 0.85);
    font-size: 14px;
    letter-spacing: 0.5px;
    text-shadow: 0 0 5px rgba(0, 180, 255, 0.4);
    transition: all 0.3s;
  }
  
  &:hover .checkbox-label {
    color: #fff;
  }
}

@keyframes check-appear {
  0% {
    opacity: 0;
    transform: rotate(-45deg) scale(0.5);
  }
  50% {
    opacity: 1;
    transform: rotate(-45deg) scale(1.2);
  }
  100% {
    opacity: 1;
    transform: rotate(-45deg) scale(1);
  }
}

// 科技感数据展示样式
.tech-info-display {
  position: relative;
  padding: 20px;
  background: rgba(16, 25, 38, 0.4);
  border-radius: 4px;
  border: 1px solid rgba(35, 85, 140, 0.3);
  box-shadow: inset 0 0 20px rgba(0, 140, 255, 0.05);
  overflow: hidden;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: 
      radial-gradient(rgba(0, 180, 255, 0.03) 1px, transparent 1px),
      radial-gradient(rgba(0, 180, 255, 0.03) 1px, transparent 1px);
    background-size: 20px 20px;
    background-position: 0 0, 10px 10px;
    pointer-events: none;
    z-index: 1;
  }
}

.tech-info-section {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-bottom: 25px;
}

.tech-info-item {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 15px;
  background: linear-gradient(
    90deg,
    rgba(20, 32, 48, 0.5) 0%,
    rgba(25, 40, 60, 0.5) 100%
  );
  border-radius: 4px;
  border: 1px solid rgba(35, 85, 140, 0.3);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  transition: all 0.3s;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 3px;
    height: 100%;
    background: linear-gradient(
      to bottom,
      rgba(0, 150, 255, 0.2),
      rgba(0, 180, 255, 0.5),
      rgba(0, 150, 255, 0.2)
    );
    border-radius: 4px 0 0 4px;
  }
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 
      0 4px 15px rgba(0, 0, 0, 0.3),
      0 0 10px rgba(0, 150, 255, 0.1);
    
    &::before {
      background: linear-gradient(
        to bottom,
        rgba(0, 150, 255, 0.3),
        rgba(0, 210, 255, 0.7),
        rgba(0, 150, 255, 0.3)
      );
    }
    
    .info-value {
      text-shadow: 0 0 8px rgba(0, 180, 255, 0.5);
    }
  }
}

.info-label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: rgba(0, 180, 255, 0.8);
  font-size: 14px;
  font-weight: 500;
  
  .label-icon {
    width: 6px;
    height: 6px;
    background: rgba(0, 180, 255, 0.8);
    border-radius: 1px;
    position: relative;
    transform: rotate(45deg);
    
    &::after {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      width: 3px;
      height: 3px;
      background: rgba(0, 210, 255, 0.9);
      transform: translate(-50%, -50%);
      border-radius: 1px;
      box-shadow: 0 0 3px rgba(0, 210, 255, 0.8);
    }
  }
}

.info-value {
  color: #ffffff;
  font-size: 15px;
  font-family: 'Consolas', monospace;
  letter-spacing: 0.5px;
  padding-left: 14px;
  position: relative;
  text-shadow: 0 0 5px rgba(0, 180, 255, 0.3);
  transition: all 0.3s;
  
  &::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 8px;
    height: 1px;
    background: rgba(0, 180, 255, 0.5);
  }
}

.tech-info-message {
  position: relative;
  z-index: 2;
  padding: 15px;
  background: rgba(18, 30, 45, 0.4);
  border: 1px solid rgba(35, 85, 140, 0.4);
  border-radius: 4px;
  margin-top: 15px;
  box-shadow: 
    inset 0 0 15px rgba(0, 0, 0, 0.2),
    0 2px 10px rgba(0, 0, 0, 0.2);
}

.message-label {
  position: relative;
  color: rgba(0, 180, 255, 0.8);
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(35, 85, 140, 0.3);
  
  .scanning-line {
    position: absolute;
    bottom: -1px;
    left: 0;
    width: 30%;
    height: 1px;
    background: rgba(0, 210, 255, 0.8);
    box-shadow: 0 0 5px rgba(0, 210, 255, 0.5);
    animation: scan-line 3s infinite alternate ease-in-out;
  }
}

.message-content {
  color: rgba(255, 255, 255, 0.85);
  font-size: 14px;
  line-height: 1.5;
  padding: 5px 0;
  font-family: 'PingFang SC', Arial, sans-serif;
  position: relative;
  
  p {
    margin: 0;
    position: relative;
    z-index: 2;
  }
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    background: linear-gradient(
      to left,
      rgba(18, 30, 45, 0) 0%,
      rgba(0, 180, 255, 0.02) 100%
    );
  }
}

// 装饰性元素
.tech-decoration {
  position: absolute;
  width: 15px;
  height: 15px;
  
  &.top-left {
    top: -1px;
    left: -1px;
    border-top: 2px solid rgba(0, 170, 255, 0.7);
    border-left: 2px solid rgba(0, 170, 255, 0.7);
    border-top-left-radius: 3px;
  }
  
  &.top-right {
    top: -1px;
    right: -1px;
    border-top: 2px solid rgba(0, 170, 255, 0.7);
    border-right: 2px solid rgba(0, 170, 255, 0.7);
    border-top-right-radius: 3px;
  }
  
  &.bottom-left {
    bottom: -1px;
    left: -1px;
    border-bottom: 2px solid rgba(0, 170, 255, 0.7);
    border-left: 2px solid rgba(0, 170, 255, 0.7);
    border-bottom-left-radius: 3px;
  }
  
  &.bottom-right {
    bottom: -1px;
    right: -1px;
    border-bottom: 2px solid rgba(0, 170, 255, 0.7);
    border-right: 2px solid rgba(0, 170, 255, 0.7);
    border-bottom-right-radius: 3px;
  }
}

.tech-scanner {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(0, 170, 255, 0.5) 50%,
    transparent 100%
  );
  opacity: 0.6;
  box-shadow: 0 0 8px rgba(0, 170, 255, 0.3);
  animation: vertical-scan 6s linear infinite;
  z-index: 10;
}

.deployedNodeItem {
  background: linear-gradient(
    180deg, 
    rgba(22, 30, 45, 0.8) 0%, 
    rgba(18, 28, 40, 0.85) 50%, 
    rgba(15, 25, 38, 0.8) 100%
  );
  border: 1px solid rgba(35, 85, 140, 0.5);
  border-radius: 4px;
  box-shadow: 
    inset 0 0 15px rgba(0, 140, 255, 0.08),
    0 0 10px rgba(0, 0, 0, 0.4),
    0 0 8px rgba(0, 150, 255, 0.1);
  overflow: hidden;
  position: relative;
  transition: all 0.3s ease;
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, 
      transparent, 
      rgba(0, 170, 255, 0.5), 
      transparent
    );
    opacity: 0.7;
    z-index: 2;
  }
  
  &::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-image: 
      linear-gradient(rgba(40, 100, 180, 0.02) 1px, transparent 1px),
      linear-gradient(90deg, rgba(40, 100, 180, 0.02) 1px, transparent 1px);
    background-size: 20px 20px;
    pointer-events: none;
    opacity: 0.5;
    z-index: 1;
  }
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 
      inset 0 0 20px rgba(0, 140, 255, 0.12),
      0 5px 15px rgba(0, 0, 0, 0.4),
      0 0 15px rgba(0, 150, 255, 0.15);
    
    .node-id {
      color: #fff;
      text-shadow: 0 0 8px rgba(0, 180, 255, 0.6);
    }
    
    .node-divider {
      background: linear-gradient(90deg, 
        transparent, 
        rgba(0, 180, 255, 0.6), 
        transparent
      );
      box-shadow: 0 0 8px rgba(0, 180, 255, 0.4);
    }
  }
  
  .node-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
    position: relative;
    z-index: 2;
  }
  
  .node-id {
    color: rgba(255, 255, 255, 0.9);
    font-family: 'Consolas', monospace;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0.5px;
    text-shadow: 0 0 5px rgba(0, 140, 255, 0.3);
    transition: all 0.3s;
  }
  
  .node-divider {
    height: 1px;
    width: 90%;
    margin: 0 auto;
    background: linear-gradient(90deg, 
      transparent, 
      rgba(0, 150, 255, 0.4), 
      transparent
    );
    transition: all 0.3s;
  }
  
  .node-content {
    padding: 12px;
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 15px;
  }
  
  .node-info {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  
  .info-grid {
    display: grid;
    gap: 8px;
  }
  
  .info-item {
    display: flex;
    align-items: center;
    gap: 8px; 
    .real-node {
      color: rgba(0, 255, 80, 0.9);
      text-shadow: 
        0 0 5px rgba(0, 255, 80, 0.5),
        0 0 10px rgba(0, 255, 80, 0.3);
      font-weight: 500;
      letter-spacing: 0.5px;
      position: relative;
      padding-left: 15px;
    } 
    .virtual-node {
      color: rgba(255, 80, 80, 0.9);
      text-shadow: 
        0 0 5px rgba(255, 80, 80, 0.5),
        0 0 10px rgba(255, 80, 80, 0.3);
      font-weight: 500;
      letter-spacing: 0.5px;
      position: relative;
      padding-left: 15px;
    }
  }
  
  .info-label {
    color: rgba(100, 180, 255, 0.9);
    font-size: 13px;
    font-weight: 500;
  }
  
  .info-value {
    color: rgba(255, 255, 255, 0.85);
    font-size: 13px;
    font-family: 'Consolas', monospace;
  }
  
  .status-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 50px;
    height: 50px;
    
    &.connected {
      .status-icon {
        filter: drop-shadow(0 0 3px #00ff4c);
      }    
      /* .status-pulse {
        border: 2px solid rgba(0, 255, 76, 0.7);
        animation: pulse-green 2s infinite;
      }  */
      .status-text {
        color: #01AA78; 
      }
    }   
    &.disconnected {
      .status-icon {
        filter: drop-shadow(0 0 3px #ff4545);
      }      
    /*   .status-pulse {
        border: 2px solid rgba(255, 69, 69, 0.7);
        animation: pulse-red 2s infinite;
      }      */
      .status-text {
        color: #DD675E;
      }
    }
  }
  
  .status-icon {
    width: 24px;
    height: 24px;
    position: relative;
    z-index: 2;
    margin-bottom: 5px;
  }
  
  .status-text {
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.5px;
    font-family: 'Consolas', monospace;
    position: relative;
    z-index: 2;
    white-space: nowrap;
  }
  
  .status-pulse {
    position: absolute;
    top: 12px; /* 调整位置以匹配图标 */
    left: 50%;
    transform: translate(-50%, -50%);
    width: 30px;
    height: 30px;
    border-radius: 50%;
    opacity: 0;
  }
  
  .node-actions {
    display: flex;
    gap: 10px;
    margin-top: 5px;
  }
}

@keyframes pulse-green {
  0% {
    transform: translate(-50%, -50%) scale(0.8);
    opacity: 0.8;
  }
  70% {
    transform: translate(-50%, -50%) scale(1.5);
    opacity: 0;
  }
  100% {
    transform: translate(-50%, -50%) scale(0.8);
    opacity: 0;
  }
}

@keyframes pulse-red {
  0% {
    transform: translate(-50%, -50%) scale(0.8);
    opacity: 0.8;
  }
  70% {
    transform: translate(-50%, -50%) scale(1.5);
    opacity: 0;
  }
  100% {
    transform: translate(-50%, -50%) scale(0.8);
    opacity: 0;
  }
}

@media screen and (max-width: 1600px) {
  .grid-cols-4 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media screen and (max-width: 1280px) {
  .grid-cols-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media screen and (max-width: 768px) {
  .grid-cols-4 {
    grid-template-columns: repeat(1, minmax(0, 1fr));
  }
}
 }

</style>
 